package org.traffometer.traffosim.gui.model;

import java.util.Observable;

import org.traffometer.common.data.TraffoPacket;
import org.traffometer.opendmtp.protocol.GPSException;
import org.traffometer.opendmtp.protocol.GeoPoint;
import org.traffometer.traffosim.ConfigWriter;
import org.traffometer.traffosim.ConfigWriterException;

/**
 * Implements a model for the ConfigWriter class. The command classes manipulate the singleton from
 * the user input.
 * 
 * @author George Lee
 * 
 */
public class ConfigModel extends Observable {
  /**The instance of ConfigWriter.*/
  private static ConfigModel theInstance = new ConfigModel();

  /**The internal configuration writer.*/
  private ConfigWriter configWriter;

  /**Data from the text fields.*/
  private String batchRate;
  private String startLat;
  private String startLong;
  private String startTime;

  private String endLat;
  private String endLong;
  private String endTime;

  /**Vehicle name.  Set to default initially.*/
  private String vehicle = "default";

  /**String displaying the status of the ConfigModel.  Default to the empty string.*/
  private String configStatus = "";

  /**Default filename to save the configuration file to.*/
  private static final String defaultPath = "lib/config/gui-test.cfg";
  
  /**Packet status message.*/
  public static final String PACKET_STATUS = "Number of packets to send: ";
  
  /**Missing fields error message.*/
  public static final String MISSING_FIELDS = "Fields may have been left blank.";
  
  /**Parsing error message.*/
  public static final String BAD_DATA = "Error in parsing data to create packet.";
  
  /**Invalid GPS data error message.*/
  public static final String BAD_GPS_DATA = "Error in creating the packet.";
  
  /**Invalid batch rate error message.*/
  public static final String INVALID_RATE = "Error, batch rate must be nonnegative.";
  
  /**Batch rate parsing error message.*/
  public static final String RATE_ERROR = "Error parsing batch rate.";
  
  /**Save status message.*/
  public static final String SAVE_STATUS = "Configuration file saved to ";
  
  /**Failed save error message.*/
  public static final String SAVE_ERROR = "Error creating the configuration file.";

  /**
   * Private constructor to make a single instance of the ConfigWriter.
   */
  private ConfigModel() {
    try {
      this.configWriter = new ConfigWriter("static", "batch");
    }

    catch (ConfigWriterException e) {
      System.err.println("Error creating the ConfigWriter.  Should not occur.");
      return;
    }
  }

  /**
   * Get the instance of ConfigModel.
   * 
   * @return The current ConfigModel instance.
   */
  public static ConfigModel getInstance() {
    return ConfigModel.theInstance;
  }

  /**
   * Set the batch rate to rate.
   * 
   * @param rate The string to set the batch transmission rate to.
   */
  public synchronized void setBatchRate(String rate) {
    this.batchRate = rate;
  }
  /**
   * Set the starting latitude to lat.
   * 
   * @param lat The string to set the starting latitude to.
   */
  public synchronized void setStartLat(String lat) {
    this.startLat = lat;
  }

  /**
   * Set the starting longitude to lat.
   * 
   * @param lng The string to set the starting latitude to.
   */
  public synchronized void setStartLong(String lng) {
    this.startLong = lng;
  }

  /**
   * Set the starting time to time.
   * 
   * @param time The string to set the start time to.
   */
  public synchronized void setStartTime(String time) {
    this.startTime = time;
  }

  /**
   * Set the ending latitude to lat.
   * 
   * @param lat The string to set the ending latitude to.
   */
  public synchronized void setEndLat(String lat) {
    this.endLat = lat;
  }

  /**
   * Set the ending longitude to lng.
   * 
   * @param lng The string to set the ending longitude to.
   */
  public synchronized void setEndLong(String lng) {
    this.endLong = lng;
  }

  /**
   * Set the ending time to time.
   * 
   * @param time The string to set the ending time to.
   */
  public synchronized void setEndTime(String time) {
    this.endTime = time;
  }

  /**
   * Set the vehicle name.
   * 
   * @param vehicle The name of the vehicle.
   */
  public synchronized void setVehicle(String vehicle) {
    this.vehicle = vehicle;
  }
  
  /**
   * Display a string representation of the current status of the ConfigModel.
   * 
   * @return A String representing the status of the ConfigModel.
   */
  public synchronized String toString() {
    return this.configStatus;
  }

  /**
   * Add the packet data to the ConfigWriter.
   */
  public synchronized void addPacket() {
    try {
      //Validate data and add it to the packet.
      TraffoPacket packet = validateData();
      this.configWriter.addPacket(packet);
      this.configStatus = PACKET_STATUS + this.configWriter.getNumPackets();
      notifyChanges();
    }
    
    //Fields have been left blank.
    catch (NullPointerException e) {
      this.configStatus = MISSING_FIELDS;
    }

    //Error parsing number data.
    catch (NumberFormatException e) {
      this.configStatus = BAD_DATA;
    }

    //Error in the data used to create the packet.
    catch (GPSException e) {
      this.configStatus = BAD_GPS_DATA;
    }
    
    //Notify observers of status.
    finally {
      notifyChanges();
    }
  }
  
  /**
   * Remove packet at a given index.
   * 
   * @param index The index of the packet to be removed.
   */
  public synchronized void removePacket(int index) {
    this.configWriter.removePacket(index);
    this.configStatus = PACKET_STATUS + this.configWriter.getNumPackets();
    notifyChanges();
  }
  
  /**
   * Return the number of packets contained by the ConfigModel.
   * 
   * @return The number of packets.
   */
  public synchronized int numPackets() {
    return this.configWriter.getNumPackets();
  }
  
  
  /**
   * Validate the data passed through the strings and add the data to the packet.
   * 
   * @return A TraffoPacket with all values initialized.
   * @throws GPSException when there is an error creating the packet.
   */
  private synchronized TraffoPacket validateData() throws GPSException {

    //Convert data into values.  Will throw NumberFormatException if there are errors.
    Double lat0 = Double.parseDouble(this.startLat);
    Double lng0 = Double.parseDouble(this.startLong);
    Long time0 = Long.parseLong(this.startTime);

    Double lat1 = Double.parseDouble(this.endLat);
    Double lng1 = Double.parseDouble(this.endLong);
    Long time1 = Long.parseLong(this.endTime);

    GeoPoint start = new GeoPoint(lat0, lng0, time0);
    GeoPoint end = new GeoPoint(lat1, lng1, time1);

    return new TraffoPacket(start, end, this.vehicle);
  }

  /**
   * Save the configuration to a filepath.
   * 
   * @param filePath The filepath to save the configuration to.
   */
  public synchronized void saveFile(String filePath) {
    
    try {
      //Validate batch rate data.
      if (this.batchRate.equalsIgnoreCase("auto")) {
        //Set batch rate to auto rate.
        this.configWriter.setBatchRate(0);
      }
      
      else {
        Integer rate = Integer.parseInt(this.batchRate);
        
        //Check for invalid rate.
        if (rate < 0) {
          this.configStatus = INVALID_RATE;
          return;
        }
        
        //Set batch rate.
        else {
          this.configWriter.setBatchRate(rate);
        }
      }
      
      //Save the configWriter to the specified path.
      if (filePath == null || filePath.equals("")) {
        //Use default save path.
        this.configWriter.save(this.defaultPath);
        this.configStatus = SAVE_STATUS + this.defaultPath;
      }
      
      else {
        this.configWriter.save(filePath);
        this.configStatus = SAVE_STATUS + filePath;
      }
    }

    //Catch errors caused by bad batch rate.
    catch (NumberFormatException e) {
      this.configStatus = RATE_ERROR;
    }
    catch (ConfigWriterException e) {
      this.configStatus = SAVE_ERROR;
    }

    finally {
      notifyChanges();
    }
  }

  /**
   * Notify observers of changes to the ConfigModel.
   */
  private void notifyChanges() {
    setChanged();
    notifyObservers(this.configStatus);
  }
}